// 本文件对Encoder类的方法进行详细定义
// @Author: zhengqi Wu
// @Description:
// @Version: 1.0.0
// @Date: 2021/2/27 9:11 下午
// @Copyright: MIN-Group；国家重大科技基础设施——未来网络北大实验室；深圳市信息论与未来网络重点实验室
//

const TLV = require("./TLV")
const MaxPacketSize = 8800

class Encoder{
    constructor() {
        this.buffer = ""
        this.left = "" // 左指针 => 指向 Prepend* 操作下一个可以插入的位置
        this.right = "" // 右指针 => 指向 Append* 操作下一个可以插入的位置
        this.isInitial = false // 是否初始化
        this.isEstimator = false // 是否仅测量编码后的长度，而不实际进行编码
        this.totalReserve = "" // 编码缓存数组的大小
    }

    /**
     * 编码测量初始化
     * 一旦调用该函数进行初始化，则调用所有的编码函数只会测量编码后的长度，不会执行实际的编码
     */
    estimatorReset(){
        this.isInitial = true
        this.isEstimator = true
        this.totalReserve = 18446744073709551615n
        this.left = 0
        this.right = 1
    }

    /**
     * 编码器初始化
     * @param totalReserve 存储编码结果的字节数组的长度 BigInt(Uint64)
     * @param reserveFromBack BigInt(Uint64)默认情况下，左右指针都指向数组的最右侧，表示totalReserve指定的所有空间都是预留给Prepend操作的
     *        reserveFromBack 可以指定需要预留多少空间给Append操作
     */
    encoderReset(totalReserve, reserveFromBack){
        totalReserve = BigInt(totalReserve)
        reserveFromBack = BigInt(reserveFromBack)
        if (reserveFromBack > totalReserve){
            // DONE 错误处理
            throw new EncoderError("EncoderReset: reserveFromBack > totalReserve")
        }
        this.isInitial = true
        this.isEstimator = false
        this.totalReserve = Number(totalReserve)
        this.buffer = new ArrayBuffer(Number(totalReserve))
        this.left = Number(totalReserve - 1n - reserveFromBack) // Safely BigInt 转 number
        this.right = Number(totalReserve - reserveFromBack) // Safely BigInt 转 number
    }

    /**
     * 用于编码的字节数组是否为空
     * @returns {boolean}
     */
    empty(){
        return this.left + 1 >= this.right
    }

    /**
     * 编码后的字节大小
     * @returns {Bigint}
     */
    size(){
        return BigInt(this.right - this.left - 1)
    }

    /**
     * 每步操作前的有效性检查
     * 1. 首先检查Encoder实例有没有初始化，没有初始化则返回错误
     * 2. 然后检查预计新增size个字节，会不会超出最大包限制，超出则返回错误
     * 3. 否则通过检查
     * @param size {Number | BigInt}
     */
    check(size){
        // DONE 错误处理
        if (!this.isInitial){
            throw new EncoderError("")
        }
        // DONE 错误处理
        if(this.size() + BigInt(size) > this.totalReserve){ // bigInt 之间的比较
            throw new EncoderError("")
        }
    }

    /**
     * prepend a byte
     * @param value{Number}
     * @returns {number}
     */
    prependByte(value){
        // DONE 错误处理
        if(this.isEstimator){
            this.left--
            return 1
        }
        // try{
            this.check(1n)
            // 前插一个字节
            let buf = new Uint8Array(this.buffer)
            buf[this.left] = Number(value)
            //有时候传进来的value值可能是bigint类型
            this.left--
            return 1
        // }catch (e){
        //     throw e
        // }
    }

    /**
     * append a byte
     * @param value {Number}
     * @returns {Number}
     */
    appendByte(value){
        // DONE 错误处理
        if(this.isEstimator){
            this.right++
            return 1
        }
        try{
            this.check(1n)
            // 前插一个字节
            let buf = Buffer.from(this.buffer)
            buf[this.right] = Number(value)
            this.right++
            return 1
        }catch (e){
            throw e
        }

    }

    /**
     * prepend a byte array
     * @param array {ArrayBuffer}
     * @param size {BigInt | Number}
     * @returns {Number}
     */
    prependByteArray(array, size){
        this.check(size)
        if (BigInt(array.byteLength) < size){
            // DONE 错误处理
            throw new EncoderError("")
        }
        if (this.isEstimator){
            this.left -= array.byteLength
            return Number(size)
        }
        // 类memcpy实现
        // let view1 = new DataView(this.buffer)
        // let view2 = new DataView(array)
        // for (let i = 0; i < Number(size); i++) {
        //     view1.setUint8(this.left - Number(size) + 1 + i, view2.getUint8(i))
        // }
        let buf1 = Buffer.from(array)
        let buf2 = Buffer.from(this.buffer)
        buf2.fill(buf1, this.left - Number(size) + 1, this.left + 1)
        this.left -= Number(size)
        return Number(size)
    }

    /**
     * append a byte array
     * @param array {ArrayBuffer}
     * @param size {BigInt}
     * @returns {Number}
     */
    appendByteArray(array, size){
        try{
            this.check(size)
            if (BigInt(array.byteLength) < size){
                // DONE 错误处理
                throw new EncoderError("")
            }
            if (this.isEstimator){
                this.right += array.byteLength
                return Number(size)
            }
            // 类memcpy实现
            let buf1 = Buffer.from(array)
            let buf2 = Buffer.from(this.buffer)
            buf2.fill(buf1, this.right, this.right + Number(size))
            this.right += Number(size)
            return Number(size)
        }catch (e){
            throw e
        }

    }

    /**
     * 前向插入一个可变长度的数字
     * @param varNumber {Number|BigInt}
     * @returns {Number}
     */
    prependVarNumber(varNumber){
        let varValue = varNumber
        if (typeof varNumber === 'bigint' && varNumber <= 4294967295n)
            varValue = Number(varNumber)
        let buffer = ''
        let view = ''
        // DONE 错误处理
        switch (true){
            case varValue < 241:
                return this.prependByte(varValue)
            case varValue <= 65535:
                buffer = new ArrayBuffer(2)
                // view = new DataView(buffer)
                // view.setUint16(0, varValue, false)
                view = Buffer.from(buffer)
                view.writeUInt16BE(varValue)
                // try {
                    this.prependByteArray(buffer, 2n)
                    this.prependByte(241)
                    return 3
                // }catch (e){
                //     throw e
                // }
            case varValue <= 4294967295:
                buffer = new ArrayBuffer(4)
                // view = new DataView(buffer)
                // view.setUint32(0, varValue, false)
                view = Buffer.from(buffer)
                view.writeUInt32BE(varValue)
                // try {
                    this.prependByteArray(buffer, 4n)
                    this.prependByte(242)
                    return 5
                // }catch (e){
                //     throw e
                // }
            default:
                buffer = new ArrayBuffer(8)
                // view = new DataView(buffer)
                // view.setBigUint64(0, varValue, false)
                view = Buffer.from(buffer)
                view.writeBigUInt64BE(varValue)
                // try{
                    this.prependByteArray(buffer, 8n)
                    this.prependByte(243)
                    return 9
                // }catch (e){
                //     throw e
                // }

        }
    }

    /**
     * 后向插入一个可变长度的数字
     * @param varNumber {Number|BigInt}
     * @returns {number}
     */
    appendVarNumber(varNumber){
        let varValue = varNumber
        if (typeof varNumber === 'bigint' && varNumber <= 4294967295n)
            varValue = Number(varNumber)
        let buffer = ''
        let view = ''
        // DONE 错误处理
        switch (true){
            case varValue < 241:
                return this.appendByte(varValue)
            case varValue <= 65535: // MAX UINT16
                buffer = new ArrayBuffer(2)
                view = Buffer.from(buffer)
                view.writeUInt16BE(varValue)
                try {
                    this.appendByte(241)
                    this.appendByteArray(buffer, 2n)
                    return 3
                }catch (e){
                    throw e
                }
            case varValue <= 4294967295: // MAX UINT32
                buffer = new ArrayBuffer(4)
                view = Buffer.from(buffer)
                view.writeUInt32BE(varValue)
                try {
                    this.appendByte(242)
                    this.appendByteArray(buffer, 4n)
                    return 5
                }catch (e){
                    throw e
                }
            default:
                buffer = new ArrayBuffer(8)
                view = Buffer.from(buffer)
                view.writeBigUInt64BE(varValue)
                try {
                    this.appendByte(243)
                    this.appendByteArray(buffer, 8n)
                    return 9
                }catch (e){
                    throw e
                }
        }
    }

    /**
     * 前向插入一个非负整数，最大为uint64::Max
     * @param value {BigInt|Number}
     * @returns {Number} 插入字节数
     */
    prependNonNegativeInteger(value){
        // try {
            let buffer = buildNonNegativeIntegerArr(value)
            return this.prependByteArray(buffer, buffer.byteLength)
        // }catch (e){
        //     throw e
        // }
    }

    /**
     * 后向插入一个非负整数，最大为uint64::Max
     * @param value {BigInt|Number}
     * @returns {Number} 插入字节数
     */
    appendNonNegativeInteger(value){
        try {
            let buffer = buildNonNegativeIntegerArr(value)
            return this.appendByteArray(buffer, buffer.byteLength)
        }catch (e){
            throw e
        }
    }

    /**
     * 根据TLV-TYPE和一个字节数组，在前向插入一个TLV Block
     * 插入顺序：BytesArray -> Length -> Type
     * @param tlvType {Number}
     * @param array {ArrayBuffer}
     * @param size {Number | BigInt}
     * @returns {Number}
     */
    prependByteArrayBlock(tlvType, array, size){
        let totalLength = 0
        // try{
            totalLength += this.prependByteArray(array, size)
            // DONE 错误处理
            totalLength += this.prependVarNumber((size))
            // DONE 错误处理
            totalLength += this.prependVarNumber(tlvType)
            return totalLength
        // }catch (e){
        //     throw e
        // }

    }

    /**
     * 根据TLV-TYPE和一个字节数组，在后向插入一个TLV Block
     * 插入顺序：Type -> Length -> BytesArray
     * @param tlvType {Number}
     * @param array {ArrayBuffer}
     * @param size {Number | BigInt}
     * @returns {Number}
     */
    appendByteArrayBlock(tlvType, array, size){
        let totalLength = 0
        try {
            totalLength += this.appendVarNumber(tlvType)
            // DONE 错误处理
            totalLength += this.appendVarNumber((size))
            // DONE 错误处理
            totalLength += this.appendByteArray(array, size)
            return totalLength
        }catch (e){
            throw e
        }
    }
    /**
     * 前向插入一个TLV Block
     * @param {Block} block 
     */
    prependBlock(block){
        // try {
            return this.prependByteArrayBlock(block.getType(), block.getValue(), block.getLength())
        // }catch (e){
        //     throw e
        // }
    }
    /**
     * 后向插入一个TLV Block
     * @param {Block} block 
     */
    appendBlock(block){
        try {
            return this.appendByteArrayBlock(block.getType(), block.getValue(), block.getLength())
        }catch (e){
            throw e
        }

    }
    /**
     * 获取编码后的字节数组
     */
    getBuffer(){
        if(!this.isInitial){
            // DONE 错误处理
            throw new EncoderError("")
        }
        if(this.isEstimator){
            return new ArrayBuffer(0)
        }
        return this.buffer.slice(this.left + 1, this.right)
    }
}

/**
 * 编码一个非负整数到一个数组当中
 * @param value => 64位非负整数 {BigInt | Number}
 * @returns {ArrayBuffer}
 */
function buildNonNegativeIntegerArr(value){
    let buffer = ''
    let view = ''
    if(value <= 255n){
        buffer = new ArrayBuffer(1)
        let buf = Buffer.from(buffer)
        buf[0] = Number(value)
        return buffer
    }
    // 2字节
    if(value <= 65536n){
        buffer = new ArrayBuffer(2)
        view = Buffer.from(buffer)
        view.writeUInt16BE(Number(value))
        return buffer
    }
    // 4字节
    if(value <= 4294967295n){
        buffer = new ArrayBuffer(4)
        view = Buffer.from(buffer)
        view.writeUInt32BE(Number(value))
        return buffer
    }
    // 8字节
    buffer = new ArrayBuffer(8)
    view = Buffer.from(buffer)
    view.writeBigUInt64BE(value)
    return buffer
}

class EncoderError extends Error{
    constructor(m){
        super(m)
        this.name = 'EncoderError'
    }

    error(){
        console.log(`${this.name}: ${this.message}`)
    }
}
module.exports = {
    Encoder:Encoder,
    MaxPacketSize:MaxPacketSize,
    buildNonNegativeIntegerArr:buildNonNegativeIntegerArr
}